﻿using System.Reflection;
using NLite.Internal;
using System;

namespace NLite.Reflection
{
    delegate object Func(object target, params object[] arguments);
    delegate void Proc(object target,params object[] arguments);

    /// <summary>
    /// 
    /// </summary>
    public static class FuncExtensions
    {

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="method"></param>
        /// <returns></returns>
        public static Action<object, T> GetAction<T>(this MethodInfo method)
        {
            Func func = null;
            if (method.DeclaringType.IsValueType)
                func = (target, value) => method.Invoke(target, value);
            else
                func = DynamicMethodFactoryProxy.Instance.CreateFuncMethod(method);

            return (target, arg) =>
            {
                try
                {
                    func(target, arg);
                }
                catch (TargetInvocationException ex)
                {
                    throw ex.InnerException;
                }
            };
        }

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <typeparam name="T2"></typeparam>
        /// <param name="method"></param>
        /// <returns></returns>
        public static Action<object, T,T2> GetAction<T,T2>(this MethodInfo method)
        {
            Func func = null;
            if (method.DeclaringType.IsValueType)
                func = (target, value) => method.Invoke(target, value);
            else
                func = DynamicMethodFactoryProxy.Instance.CreateFuncMethod(method);

            return (target, arg,arg2) =>
            {
                try
                {
                    func(target, arg,arg2);
                }
                catch (TargetInvocationException ex)
                {
                    throw ex.InnerException;
                }
            };
        }

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <typeparam name="T2"></typeparam>
        /// <typeparam name="T3"></typeparam>
        /// <param name="method"></param>
        /// <returns></returns>
        public static Action<object, T, T2,T3> GetAction<T, T2,T3>(this MethodInfo method)
        {
            Func func = null;
            if (method.DeclaringType.IsValueType)
                func = (target, value) => method.Invoke(target, value);
            else
                func = DynamicMethodFactoryProxy.Instance.CreateFuncMethod(method);

            return (target, arg, arg2,arg3) =>
            {
                try
                {
                    func(target, arg, arg2, arg3);
                }
                catch (TargetInvocationException ex)
                {
                    throw ex.InnerException;
                }
            };
        }

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="TReturn"></typeparam>
        /// <param name="method"></param>
        /// <returns></returns>
        public static Func<object, T, TReturn> GetFunc<T, TReturn>(this MethodInfo method)
        {
            Func func = null;
            if (method.DeclaringType.IsValueType)
                func = (target, value) => method.Invoke(target, value);
            else
                func = DynamicMethodFactoryProxy.Instance.CreateFuncMethod(method);

            return (target, arg) =>
            {
                try
                {
                    return (TReturn)func(target, arg);
                }
                catch (TargetInvocationException ex)
                {
                    throw ex.InnerException;
                }
            };
        }

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <typeparam name="T2"></typeparam>
        /// <typeparam name="TReturn"></typeparam>
        /// <param name="method"></param>
        /// <returns></returns>
        public static Func<object, T,T2, TReturn> GetFunc<T,T2, TReturn>(this MethodInfo method)
        {
            Func func = null;
            if (method.DeclaringType.IsValueType)
                func = (target, value) => method.Invoke(target, value);
            else
                func = DynamicMethodFactoryProxy.Instance.CreateFuncMethod(method);

            return (target, arg,arg2) =>
            {
                try
                {
                    return (TReturn)func(target, arg,arg2);
                }
                catch (TargetInvocationException ex)
                {
                    throw ex.InnerException;
                }
            };
        }

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <typeparam name="T2"></typeparam>
        /// <typeparam name="T3"></typeparam>
        /// <typeparam name="TReturn"></typeparam>
        /// <param name="method"></param>
        /// <returns></returns>
        public static Func<object, T, T2, T3,TReturn> GetFunc<T, T2,T3, TReturn>(this MethodInfo method)
        {
            Func func = null;
            if (method.DeclaringType.IsValueType)
                func = (target, value) => method.Invoke(target, value);
            else
                func = DynamicMethodFactoryProxy.Instance.CreateFuncMethod(method);

            return (target, arg, arg2,arg3) =>
            {
                try
                {
                    return (TReturn)func(target, arg, arg2, arg3);
                }
                catch (TargetInvocationException ex)
                {
                    throw ex.InnerException;
                }
            };
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="method"></param>
        /// <param name="target"></param>
        /// <param name="args"></param>
        /// <returns></returns>
        public static object FastFuncInvoke(this MethodInfo method, object target, params object[] args)
        {
        	if (args == null)
        		args = new object[0];

        	if (target != null && target.GetType().IsValueType)
        		return method.Invoke(target, args);

        	var func = DynamicMethodFactoryProxy.Instance.CreateFuncMethod(method);
        	
        	try
        	{
        		return func(target, args);
        	}
        	catch(TargetInvocationException ex)
        	{
        		throw ex.InnerException;
        	}
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="method"></param>
        /// <param name="target"></param>
        /// <param name="args"></param>
        public static void FastProcInvoke(this MethodInfo method, object target, params object[] args)
        {
        	if (args == null)
        		args = new object[0];

        	if (target != null && target.GetType().IsValueType)
        		method.Invoke(target, args);

        	var func = DynamicMethodFactoryProxy.Instance.CreateProcMethod(method);

        	try
        	{
        		func(target, args);
        	}
        	catch(TargetInvocationException ex)
        	{
        		throw ex.InnerException;
        	}
        }
    }
}
